package com.google.code.jtasks.client.ui.commands;

import com.google.code.jtasks.client.exceptions.TerminateException;
import com.google.code.jtasks.client.services.TaskService;
import com.google.code.jtasks.client.ui.Command;
import com.google.code.jtasks.client.ui.ScreenHelper;
import com.google.code.jtasks.common.data.Task;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.inject.Inject;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

import java.util.List;

/**
 * Команда на создание новой задачи. Запрашивает название задачи, дату начала и дату завершения. Если пользователь
 * нажимает Escape во время ввода любого из этих значений, команда отменяется. Дату можно ввести в любом из следующих
 * форматов:
 * <pre>
       yyyy-MM-dd
       yyyy-MM-dd HH
       yyyy-MM-dd HH:mm
       yyyy-MM-dd HH:mm:ss
 * </pre>
 */
public class NewTaskCommand implements Command {
    private final ScreenHelper helper;
    private final TaskService taskService;

    @Inject
    NewTaskCommand(ScreenHelper helper, TaskService taskService) {
        this.helper = helper;
        this.taskService = taskService;
    }

    @Override
    public String name() {
        return "Create new task";
    }

    @Override
    public String description() {
        return "Create a new task and send it on the server";
    }

    @Override
    public void execute() throws TerminateException {
        int y = 2;
        helper.drawString(5, y, "Enter task name:");
        helper.refresh();
        Optional<String> taskName = helper.readStringWithOutputAt(5, y+1);
        if (!taskName.isPresent()) {
            return;
        }
        y += 2;

        DateTime startTime = readDate(y, "Enter start time");
        if (startTime == null) {
            return;
        }
        y += 2;

        DateTime endTime = readDate(y, "Enter end time");
        if (endTime == null) {
            return;
        }
        y += 3;

        sendTaskAndWait(y, new Task(taskName.get(), startTime, endTime));
    }

    /**
     * Вызывает {@link TaskService} и выводит сообщение пользователю.
     *
     * @param y вертикальное смещение, на котором выводить сообщение
     * @param task объект задачи, которую нужно создать
     */
    private void sendTaskAndWait(int y, Task task) {
        String message = "Adding the task... ";
        helper.drawString(5, y, message);
        helper.refresh();

        taskService.addTask(task);

        helper.drawString(5 + message.length(), y, "Done.");

        helper.waitForKey(y+1);
    }

    /**
     * Считывает дату с клавиатуры. При этом обеспечивается повторный ввод в случае ошибки ввода.
     * @param y вертикальное смещение, на котором нужно выводить сообщение
     * @param message сообщение, поясняющее дату
     * @return объект даты и времени, или null, если дата не была введена
     */
    private DateTime readDate(int y, String message) {
        helper.drawString(5, y, message + " (yyyy-MM-dd[ HH[:mm[:ss]]]):");

        DateTime date;
        boolean firstTime = true;
        while (true) {
            helper.clearLine(y + 1);
            helper.refresh();

            Optional<String> input = helper.readStringWithOutputAt(
                5, y+1, firstTime ? DateTime.now().toString("yyyy-MM-dd") : ""
            );
            if (!input.isPresent()) {
                return null;
            }

            date = tryConvert(input.get());
            if (date != null) {
                break;
            }

            helper.drawString(5, y + 3, "Invalid input, try again");
            helper.refresh();
            firstTime = false;
        }

        helper.clearLine(y + 3);
        return date;
    }

    private static final List<DateTimeFormatter> FORMATTERS = ImmutableList.of(
        DateTimeFormat.forPattern("yyyy-MM-dd"),
        DateTimeFormat.forPattern("yyyy-MM-dd HH"),
        DateTimeFormat.forPattern("yyyy-MM-dd HH:mm"),
        DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss")
    );

    /**
     * Пробует подряд несколько форматтеров для даты и пытается распарсить строку. Если получается,
     * то строка возвращается как объект даты и времени, иначе null
     *
     * @param string строка с датой
     * @return объект даты и времени, или null, если преобразование не было успешным
     */
    private DateTime tryConvert(String string) {
        for (DateTimeFormatter formatter : FORMATTERS) {
            try {
                return formatter.parseDateTime(string);
            } catch (IllegalArgumentException e){
                // Ignore parsing error
            }
        }
        return null;
    }
}
